﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;

namespace Escuela
{

    interface iPersona
    {
        XElement ToXml();
        //Persona Parse(XElement data);
    }

    class Persona : iPersona 
    {
        private string id;

        #region Propiedades

        /// <summary>
        /// Un identificador unico para cada persona, un string conteniendo un GUID
        /// </summary>
        public string Id
        {
            get { return id; }
            set { id = value; }
        }


        private string nombre;
        /// <summary>
        /// El nombre de la persona.
        /// </summary>
        public string Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }

        private string matricula;

        /// <summary>
        /// La matricula que identifica a esta persona dentro de la escuela
        /// </summary>
        public string Matricula
        {
            get { return matricula; }
            set { matricula = value; }
        }

        private string telefono;

        /// <summary>
        /// El numero de la persona
        /// </summary>
        public string Telefono
        {
            get { return telefono; }
            set { telefono = value; }
        }


        private short edad;

        /// <summary>
        /// La edad de la persona
        /// </summary>
        public short Edad
        {
            get { return edad; }
            set { edad = value; }
        }

        private Sexos? sexo;

        /// <summary>
        /// El sexo de la persona
        /// Cómo no es necesario introducir este dato en el sistema, este campo es "nullable" 
        /// Si el valor es nulo, obtendremos NoEspecificado cómo resultado.
        /// </summary>
        public Sexos? Sexo
        {
            get { return sexo ?? Sexos.NoEspecificado; }
            set { sexo = value; }
        }

        private TiposDePersona tipoDePersona;

        /// <summary>
        /// El tipo de persona, es decir Alumno o docente.
        /// </summary>
        public TiposDePersona TipoDePersona
        {
            get { return tipoDePersona; }
            set { tipoDePersona = value; }
        }

        /// <summary>
        /// Todos los sexos en inglés y español.
        /// </summary>
        public enum Sexos
        {
            Masculino = 0,
            Male = 0,
            Femenino = 1,
            Female = 1,
            NoEspecificado = 2,
            NotSpecified = 2
        }

        public enum TiposDePersona
        {
            Docente = 0,
            Alumno = 1
        }

        #endregion
        
        #region Metodos
        /// <summary>
        /// Obtiene un elemento Xml que representa a la actual persona
        /// </summary>
        /// <returns></returns>
        public virtual XElement ToXml()
        {
            //Generar un elemento Xml con el nombre "Persona"
            XElement result = new XElement("Persona");

            //Agregar los atributos ID, y Tipo
            result.Add(new XAttribute("ID", this.id),
                new XAttribute("Tipo", this.TipoDePersona.ToString())
                );

            //Agregar los elementos
            result.Add(new XElement("Nombre", this.Nombre)); //Nombre
            result.Add(new XElement("Matricula", this.Matricula)); //Matricula
            result.Add(new XElement("Telefono", this.Telefono)); //Telefono
            result.Add(new XElement("Edad", this.Edad)); //Edad
            result.Add(new XElement("Sexo", this.Sexo.ToString())); //Sexo

            return result;
        }

        /// <summary>
        /// Obtiene un elemento Xml que representa a una persona y obtiene un dato de tipo Persona
        /// (Este proceso se aplica a cualquier tipo de persona)
        /// </summary>
        /// <param name="Data"></param>
        /// <returns></returns>
        public static Persona Parse(XElement Data)
        {
            Persona per = null; /*Una variable de tipo persona sin ningun valor asignado.
                                 * Esta variable será el resultado */


            //Determinamos el tipo de la persona.
            switch (ParseTipo(Data.Attribute("Tipo").Value))
            {
                case TiposDePersona.Alumno: //La persona es un alumno
                    Alumno __per = new Alumno(); /*Creamos una variable, está ves será del tipo Alumno
                                                  * Sin embargo, esta hereda a la clase persona 
                                                  */

                    //Obtenemos los elementos y atributos que conforman a esta variable y los 
                    //Convertimos al tipo de variable que necesitemos
                    __per.Edad = short.Parse((string)Data.Element("Edad"));

                    __per.Sexo = ParseSexo((string)Data.Element("Sexo")); //Obtener y convertir el elemento Sexo
                    __per.TipoDePersona = ParseTipo((string)Data.Attribute("Tipo")); //Obtener y converirl el elemento TipoDePersona

                    __per.Id = (string)Data.Attribute("ID"); //Obtener el atribute ID
                    __per.Matricula = (string)Data.Element("Matricula"); //Obtener el elemento Matricula
                    __per.Nombre = (string)Data.Element("Nombre"); //Obtener el elemento Nombre
                    __per.Telefono = (string)Data.Element("Telefono"); //Obtener el elemento telefono

                    //Obtener todos los descendentes en el elemento Materias
                    XElement Materias = Data.Element("Materias") ?? new XElement("Materias");
                    foreach (XElement item in Materias.Descendants("Materia"))
                    {
                        __per.Materias.Add(Materia.Parse(item)); //Y agregarlos a la lista de materias dentro de la variable actual
                    }

                    per = __per; //Asignar al resultado.
                    break;

                case TiposDePersona.Docente: // La persona es de tipo docente.
                    Docente __doc = new Docente(); //Instanciamos la variable de tipo persona como docente.

                    //Obtenemos los valores y los convertimos a su tipo correcto

                    __doc.Edad = short.Parse((string)Data.Element("Edad"));//Obtenemos la edad

                    __doc.Sexo = ParseSexo((string)Data.Element("Sexo")); //El sexo
                    __doc.TipoDePersona = ParseTipo((string)Data.Attribute("Tipo")); //El tipo de la persona

                    __doc.Id = (string)Data.Attribute("ID"); // Identificador unico
                    __doc.Matricula = (string)Data.Element("Matricula"); // Matricula, un identificador más amigable
                    __doc.Nombre = (string)Data.Element("Nombre"); // Nombre de la persona
                    __doc.Telefono = (string)Data.Element("Telefono"); // & su telefono

                    XElement materias = Data.Element("Materias") ?? new XElement("Materias");

                    //Buscaremos todos los descendentes con nombre "Materia" en el elemento materias.
                    foreach (XElement item in materias.Descendants("Materia"))
                    {
                        //Entonces convertiremos el elemento Xml en una variable de tipo Materia.
                        __doc.Asignaturas.Add(Materia.Parse(item));
                    }

                    //Buscamos todos los descendentes con nombre "Practica" en el elemento "Practicas"
                    foreach (XElement item in Data.Elements("Practicas").Descendants("Practica"))
                    {
                        __doc.Practicas.Add((string)item); //Esto es sólo un string.
                    }
                    per = __doc; //Asignamos al resultado.
                    break;
            }

            return per;
        }

        /// <summary>
        /// Toma un string y regresa su valor a base de un enumerador.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static TiposDePersona ParseTipo(string value)
        {
            return (TiposDePersona)Enum.Parse(typeof(TiposDePersona), value);
        }

        /// <summary>
        /// Toma un string y regresa su valor en base de un enumerador.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static Sexos ParseSexo(string value)
        {

            return (Sexos)Enum.Parse(typeof(Sexos), value);
        }
        #endregion

        #region Operadores
        public static bool operator ==(Persona Persona, string Matricula)
        {
            return (Persona.Matricula == Matricula);
        }

        public static bool operator !=(Persona persona, string Matricula)
        {
            return !(persona.Matricula == Matricula);
        }

        public override bool Equals(object obj)
        {
            return (this == obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        #endregion

    }//Clas Persona

    class Alumno : Persona, iPersona
    {
        #region Propiedades
        private List<Materia> materias = new List<Materia>();

        /// <summary>
        /// Obtiene o asigna una lista de las materias que el alumno toma.
        /// </summary>
        public List<Materia> Materias
        {
            get
            {
                if (materias == null) materias = new List<Materia>();
                return materias;
            }
            set { materias = value; }
        }
        #endregion

        #region Metodos
        /// <summary>
        /// Instanciar una clase de tipo Alumno.
        /// </summary>
        public Alumno()
        {
            materias = new List<Materia>();
        }

        /// <summary>
        /// Obtiene la primera materia con el nombre especificado.
        /// </summary>
        /// <param name="Nombre"></param>
        /// <returns></returns>
        public Materia MateriaByName(string Nombre)
        {
            return materias.SingleOrDefault((Materia m) => m.Nombre == Nombre);
        }

        /// <summary>
        /// Obtiene la primera materia con el ID especificado.
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        public Materia MateriaByID(int ID)
        {
            return Materias.SingleOrDefault((Materia m) => m.ID == ID);
        }

        /// <summary>
        /// Obtiene un elemento Xml que representa a este alumno. 
        /// Este metodo obtendrá también las propiedades generales en 
        /// la clase Persona que es heredada.
        /// </summary>
        /// <returns></returns>
        public override XElement ToXml()
        {
            XElement result = base.ToXml(); //Obtener las propiedades especificadas en la clase persona.
            XElement Materias = new XElement("Materias");

            //& agregar las materias.
            foreach (Materia item in this.Materias)
            {
                Materias.Add(item.ToXml());
            }

            result.Add(Materias);

            return result;
        }
        #endregion

    }

    class Docente : Persona, iPersona 
    {
        #region Propiedades
        private List<Materia> asignaturas = new List<Materia>();

        /// <summary>
        /// Obtiene o asigna una lista de las asignaturas que el maestro imparte.
        /// </summary>
        public List<Materia> Asignaturas
        {
            get
            {
                if (asignaturas == null) asignaturas = new List<Materia>();
                return asignaturas;
            }
            set { asignaturas = value; }
        }

        private List<string> practicas;

        /// <summary>
        /// Obtiene o asigna una lista de las practicas que el maestro tiene pendientes.
        /// </summary>
        public List<string> Practicas
        {
            get
            {
                if (practicas == null) practicas = new List<string>();
                return practicas;
            }
            set { practicas = value; }
        }
        #endregion

        #region Metodos
        /// <summary>
        /// Obtiene un elemento Xml que representa a este docente. 
        /// Este metodo sobrecarga un metodo virtual en la clase heredada
        /// Persona, de forma que este metodo también obtendrá todas las
        /// propiedades generales de la persona en el elemento Xml.
        /// </summary>
        /// <returns></returns>
        public override XElement ToXml()
        {
            XElement result = base.ToXml(); /* Obtenemos las propiedades generales
                                             * Que se generarán en la clase Persona 
                                             * y las agregamos a nuestro resultado */

            XElement Asignaturas = new XElement("Asignaturas"); //Generar un elemento Xml para las asignaturas

            //Agregar las asignaturas.
            foreach (Materia item in this.Asignaturas)
            {
                Asignaturas.Add(item.ToXml());
            }

            result.Add(Asignaturas);


            XElement Practicas = new XElement("Practicas");/*Generar un elemento Xml para las practicas que el maestro
                                                            * imparte */

            foreach (string item in this.Practicas)
            {
                //Las practicas serán strings simples.
                Practicas.Add(new XElement("Practica"), item);
            }

            result.Add(Practicas);

            return result;
        }

        public Docente()
        {
            this.asignaturas = new List<Materia>();
        }
        #endregion

    }

    /// <summary>
    /// Representa una materia
    /// </summary>
    public class Materia : IEquatable<Materia>
    {
        #region Propiedades
        Int32 id;

        /// <summary>
        /// Obtiene o asigna el ID unico de la materia
        /// </summary>
        public Int32 ID
        {
            get { return id; }
            set { id = value; }
        }

        string nombre;

        /// <summary>
        /// Obtiene o asigna el nombre de la materia.
        /// </summary>
        public string Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }

        string salon;

        /// <summary>
        /// Obtiene o asigna en que salon se imparte esta materia.
        /// </summary>
        public string Salon
        {
            get { return salon; }
            set { salon = value; }
        }

        string profesor;

        /// <summary>
        /// Obtiene o asigna el nombre del profesor que imparte esta materia.
        /// </summary>
        public string Profesor
        {
            get { return profesor; }
            set { profesor = value; }
        }

        DateTime horaInicio;

        /// <summary>
        /// Obtiene o asigna la hora a la que inicia esta materia.
        /// </summary>
        public DateTime HoraInicio
        {
            get { return horaInicio; }
            set { horaInicio = value; }
        }

        DateTime horaTermino;

        /// <summary>
        /// Obtiene o asigna la hora a la que termina esta materia.
        /// </summary>
        public DateTime HoraTermino
        {
            get { return horaTermino; }
            set { horaTermino = value; }
        }

        private string instanceID;

        public string InstanceID
        {
            get { return instanceID; }
        }
        #endregion

        #region Metodos
        /// <summary>
        /// Obtiene un elemento Xml que representa esta materia.
        /// </summary>
        /// <returns></returns>
        public XElement ToXml()
        {
            //Generar un elemento Xml y agregar atributos.
            XElement result =
                new XElement("Materia", new XAttribute("ID", this.ID.ToString()));

            //Agregar los elementos.
            result.Add(
                new XElement("Nombre", this.Nombre), // Nombre
                new XElement("Salon", this.Salon), // Salon
                new XElement("Profesor", this.Profesor), // Profesor
                new XElement("HoraDeInicio", this.HoraInicio.ToShortTimeString()), // Hora de inicio (En formato corto)
                new XElement("HoraDeTermino", this.HoraTermino.ToShortTimeString())  // Hora de conclusion (En formato corto)
                );

            return result;
        }

        /// <summary>
        /// Toma un elemento Xml y lo convierte a su equivalente en tipo Materia
        /// </summary>
        /// <param name="Data"></param>
        /// <returns></returns>
        public static Materia Parse(XElement Data)
        {
            Materia result = new Materia(); //Este será el resultado

            //Obtenemos los valores que deseamos y los convertimos a su tipo correcto

            result.ID = int.Parse((string)Data.Attribute("ID")); //Atributo ID

            result.Nombre = (string)Data.Element("Nombre"); //Elemento Nombre
            result.Profesor = (string)Data.Element("Profesor"); //Elemento profesor
            result.Salon = (string)Data.Element("Salon"); //Elemento Salon

            result.HoraInicio = DateTime.Parse((string)Data.Element("HoraDeInicio")); //Hora de inicio
            result.HoraTermino = DateTime.Parse((string)Data.Element("HoraDeTermino")); //Hora de conclusion.


            return result;
        }

        public Materia()
        {
            this.instanceID = Guid.NewGuid().ToString();
        }
        #endregion        

        #region Operadores
        public static bool operator ==(Materia materia, string Nombre)
        {
            return (materia.Nombre == Nombre);
        }

        public static bool operator !=(Materia materia, string Nombre)
        {
            return (materia.Nombre != Nombre);
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /* Cuando hago comparaciones para hacer queries .NET identificará 
         * que tiene que usar este metodo para determinar si los objetos son iguales.
         * Si ambos contienen el mismo InstanceID regresará true 
         * esto ocurre porque esta clase hereda IEquatable<T> 
         */

        public bool Equals(Materia materia)
        {

            return string.Equals(this.InstanceID ?? "",
                materia.InstanceID ?? "");
        }

        /*Esto tendría que regresar true si las materias tienen el mismo nombre
         * Aunque tengan InstanceIDs diferentes y por lo tanto sean considerados 
         * objetos distintos. 
         * La mala noticia es que no logro hacer que el "comparer" entre a este metodo
         */
        public bool Equals(string materia)
        {
            return string.Equals(this.Nombre ?? "",
                materia ?? "");
        }
        #endregion

    }

    

    /// <summary>
    /// Provee herramientas necesarias para manejar y
    /// administrar un documento Xml 
    /// que contiene la información de todos los alumno y maestros 
    /// de una escuela. 
    /// </summary>
    class SchoolList : List<Persona>
    {

        #region Propiedades

        string path;

        /// <summary>
        /// Obtiene o asigna el archivo en que se encuentra el documento 
        /// Xml a usar.
        /// </summary>
        public string Path
        {
            get { return path; }
            set { path = value; }
        }

        public SchoolList() { } 

        #endregion

        #region Metodos
        /// <summary>
        /// Carga un archivo conteniendo un documento Xml 
        /// Y genera una lista que se alacenará en este objeto.
        /// </summary>
        /// <param name="Path"></param>
        public void Load(string Path)
        {
            this.Path = Path;

            //try
            //{
            XDocument xDoc = XDocument.Load(Path);
            var Data = (from item in xDoc.Descendants("Persona") select item);

            foreach (XElement item in Data)
            {
                Persona per = Persona.Parse(item);
                this.Add(per);
            }
            //}
            //catch (Exception ex)
            //{
            //    Console.WriteLine(ex.Message);
            //}
        }

        /// <summary>
        /// Obtiene o asigna un elemento de la lista que tenga el nombre especificado
        /// (Esta propiedad es defaul)
        /// </summary>
        /// <param name="Matricula"></param>
        /// <returns></returns>
        public Persona this[string Matricula]
        {
            get
            {
                return this.SingleOrDefault((Persona p) => p.Matricula == Matricula);
            }
            set
            {
                var element = this.SingleOrDefault((Persona p) => p.Matricula == Matricula);
                this[this.IndexOf(element)] = element;
            }
        }

        /// <summary>
        /// Agrega una materia especificado la matricula del alumno
        /// a la cual se desea agregar el elemento y el nombre de una materia.
        /// </summary>
        /// <param name="Matricula"></param>
        /// <param name="Materia"></param>
        public void AgregarMateria(string Matricula, Materia Materia)
        {
            ((Alumno)this[Matricula]).Materias.Add(Materia);
        }

        /// <summary>
        /// Elimina una materia especificado la matricula del alumno 
        /// Y el nombre de la materia a eliminar.
        /// </summary>
        /// <param name="Matricula"></param>
        /// <param name="Materia"></param>
        public void EliminarMateria(string Matricula, string Materia)
        {
            Materia __materia =
                ((Alumno)this[Materia]).Materias.SingleOrDefault((Materia m) => m.Nombre == Materia);

            ((Alumno)this[Matricula]).Materias.Remove(__materia);
        }

        /// <summary>
        /// Agrega una asignatura especificando la matricula del docente 
        /// </summary>
        /// <param name="Matricula">La matricula del docente.</param>
        /// <param name="Materia">La asignatura que se desea agregar</param>
        public void AgregarAsignatura(string Matricula, Materia Materia)
        {
            ((Docente)this[Matricula]).Asignaturas.Add(Materia);
        }

        /// <summary>
        /// Elimina una asignatura de un docente especificando su matricula
        /// Y el nombre de la materia
        /// </summary>
        /// <param name="Matricula">La matricula del docente.</param>
        /// <param name="Materia">El nombre de la materia.</param>
        public void EliminarAsignatura(string Matricula, string Materia)
        {
            Materia __materia =
                ((Docente)this[Materia]).Asignaturas.SingleOrDefault((Materia m) => m.Nombre == Materia);
            ((Docente)this[Matricula]).Asignaturas.Remove(__materia);
        }

        public List<Alumno> Alumnos()
        {
            List<Alumno> query = (from item in this
                                  where item.TipoDePersona == Persona.TiposDePersona.Alumno
                                  select (Alumno)item).ToList();

            return query;
        }

        public List<Docente> Docentes()
        {
            List<Docente> query = (from item in this
                                   where item.TipoDePersona == Persona.TiposDePersona.Docente
                                   select (Docente)item).ToList();

            return query;
        }

        /// <summary>
        /// Obtiene un elemento de la lista y lo convierte a 
        /// un tipo de variable especificado. 
        /// (Sólo son validas las conversiones a Escuela.Persona 
        /// Escuela.Alumno y Escuela.Docente)
        /// </summary>
        /// <typeparam name="T">El tipo a convertir</typeparam>
        /// <param name="Matricula">La matricula de la persona.</param>
        /// <returns></returns>
        public T GetAndCast<T>(string Matricula)
        {
            // =(
            return (T)(object)this[Matricula];
        }

        /// <summary>
        /// Genera un elemento Xml que representa esta lista de personas.
        /// </summary>
        /// <returns></returns>
        public XElement ToXml()
        {
            XElement result = new XElement("Personas");
            foreach (Persona item in this)
            {
                result.Add(item.ToXml());
            }

            return result;
        }

        /// <summary>
        /// Genera un documento Xml que contiene todos los elementos
        /// de esta lista y acontinuación los guarda en disco duro.
        /// </summary>
        public void Save()
        {
            XDocument xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
            xDoc.Add(this.ToXml());

            System.IO.StreamWriter sw = new System.IO.StreamWriter(this.Path);
            sw.Write(xDoc.ToString());
            sw.Close();
        }
        #endregion

    }
}